#include "Application.h"
#include <D3Dcompiler.h>
#include <cassert>
#include <climits>
#include <iostream>
#include "vec.inl"
#include "Geometry.h"

Application* Application::app = nullptr;

Application::Application(HINSTANCE hInstance, int nCmdShow, UINT width, UINT height)
{
	assert(app == nullptr);
	m_hInst = NULL;
	m_nCmdShow = nCmdShow;
	m_hWnd = NULL;
	m_width = width;
	m_height = height;
	m_driverType = D3D_DRIVER_TYPE_NULL;
	m_featureLevel = D3D_FEATURE_LEVEL_11_0;
	m_pDevice = NULL;
	m_pImContext = NULL;
	m_pSwapChain = NULL;
	//backbuffer, depth stencil
	m_pRenderTargetView = NULL;
	m_pDepthStencil = NULL;
	m_pDepthStencilView = NULL;
	//start init
	HRESULT hr;
	hr = InitWindow();
	assert(SUCCEEDED(hr));
	hr = InitDevice();
	assert(SUCCEEDED(hr));
	app = this;

	for(auto& state : m_keysDown) state = false;

	m_timer.Reset();
	m_timer.Start();

	m_baseSpeed = 1.f;
	m_speed = m_baseSpeed;
	m_acc = .2f;
}

Application::~Application()
{
	if( m_pImContext ) m_pImContext->ClearState();
	if( m_pRenderTargetView ) m_pRenderTargetView->Release();
	if( m_pSwapChain ) m_pSwapChain->Release();
	if( m_pImContext ) m_pImContext->Release();
	if( m_pDevice ) m_pDevice->Release();

	for(auto& mesh : m_meshes) delete mesh;
	for(auto& line : m_lines) delete line;
	app = nullptr;
}

HRESULT Application::InitWindow()
{
	// Register class
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof( WNDCLASSEX );
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = MsgProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = m_hInst;
	wcex.hIcon = LoadIcon( m_hInst, ( LPCTSTR )IDI_TUTORIAL1 );
	wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
	wcex.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = L"D3DWindowClass";
	wcex.hIconSm = LoadIcon( wcex.hInstance, ( LPCTSTR )IDI_TUTORIAL1 );
	if( !RegisterClassEx( &wcex ) )
		return E_FAIL;

	// Create window
	RECT rc = { 0, 0, m_width, m_height };
	AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );
	m_hWnd = CreateWindow( L"D3DWindowClass", L"D3DWindow", WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, CW_USEDEFAULT, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, m_hInst,
		NULL );
	if( !m_hWnd )
		return E_FAIL;

	ShowWindow( m_hWnd, m_nCmdShow );

	return S_OK;
}

HRESULT Application::InitDevice()
{
	HRESULT hr = S_OK;

	UINT createDeviceFlags = 0;
#ifdef _DEBUG
	createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	D3D_DRIVER_TYPE driverTypes[] =
	{
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_WARP,
		D3D_DRIVER_TYPE_REFERENCE,
	};

	D3D_FEATURE_LEVEL featureLevels[] =
	{
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
	};
	UINT numFeatureLevels = ARRAYSIZE( featureLevels );

	DXGI_SWAP_CHAIN_DESC sd;
	ZeroMemory( &sd, sizeof( sd ) );
	sd.BufferCount = 1;
	sd.BufferDesc.Width = m_width;
	sd.BufferDesc.Height = m_height;
	sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
	sd.BufferDesc.RefreshRate.Numerator = 60;
	sd.BufferDesc.RefreshRate.Denominator = 1;
	sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
	sd.OutputWindow = m_hWnd;
	sd.SampleDesc.Count = 1;
	sd.SampleDesc.Quality = 0;
	sd.Windowed = TRUE;

	for(auto driverType : driverTypes){
		m_driverType = driverType;
		hr = D3D11CreateDeviceAndSwapChain( 
			NULL,
			m_driverType,
			NULL,
			createDeviceFlags, 
			featureLevels, 
			numFeatureLevels,
			D3D11_SDK_VERSION, 
			&sd, 
			&m_pSwapChain, 
			&m_pDevice, 
			&m_featureLevel, 
			&m_pImContext 
			);
		if( SUCCEEDED( hr ) )
			break;
	}
	if( FAILED( hr ) )
		return hr;

	// Create a render target view
	ID3D11Texture2D* pBackBuffer = NULL;
	hr = m_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), ( LPVOID* )&pBackBuffer );
	if( FAILED( hr ) )
		return hr;

	hr = m_pDevice->CreateRenderTargetView( pBackBuffer, NULL, &m_pRenderTargetView );
	pBackBuffer->Release();
	if( FAILED( hr ) )
		return hr;

	// Create depth stencil texture
	D3D11_TEXTURE2D_DESC descDepth;
	ZeroMemory( &descDepth, sizeof(descDepth) );
	descDepth.Width = m_width;
	descDepth.Height = m_height;
	descDepth.MipLevels = 1;
	descDepth.ArraySize = 1;
	descDepth.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	descDepth.SampleDesc.Count = 1;
	descDepth.SampleDesc.Quality = 0;
	descDepth.Usage = D3D11_USAGE_DEFAULT;
	descDepth.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	descDepth.CPUAccessFlags = 0;
	descDepth.MiscFlags = 0;
	hr = m_pDevice->CreateTexture2D( 
		&descDepth, 
		NULL, 
		&m_pDepthStencil );
	if( FAILED( hr ) )
		return hr;

	// Create the depth stencil view
	D3D11_DEPTH_STENCIL_VIEW_DESC descDSV;
	ZeroMemory( &descDSV, sizeof(descDSV) );
	descDSV.Format = descDepth.Format;
	descDSV.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
	descDSV.Texture2D.MipSlice = 0;
	hr = m_pDevice->CreateDepthStencilView( m_pDepthStencil, &descDSV, &m_pDepthStencilView );
	if( FAILED( hr ) )
		return hr;

	m_pImContext->OMSetRenderTargets( 1, &m_pRenderTargetView, m_pDepthStencilView );

	// Setup the viewport
	D3D11_VIEWPORT vp;
	vp.Width = (FLOAT)m_width;
	vp.Height = (FLOAT)m_height;
	vp.MinDepth = 0.0f;
	vp.MaxDepth = 1.0f;
	vp.TopLeftX = 0;
	vp.TopLeftY = 0;
	m_pImContext->RSSetViewports( 1, &vp );

	return S_OK;
}

void Application::Render()
{
	// just clear the backbuffer
	static float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; //red,green,blue,alpha
	m_pImContext->ClearRenderTargetView( m_pRenderTargetView, ClearColor );
	m_pImContext->ClearDepthStencilView( m_pDepthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
	
	//draw meshes
	MeshCBuffer meshcb;
	meshcb.proj = m_camera.GetProj();
	meshcb.proj = XMMatrixTranspose(meshcb.proj);
	for(auto& mesh : m_meshes){
		meshcb.worldView = mesh->m_world * m_camera.GetView();
		meshcb.worldView = XMMatrixTranspose(meshcb.worldView);
		m_meshShader.Update(&meshcb, TODO, TODO);
		mesh->Render();
	}
	//draw lines
	LineCBuffer linecb;
	for(auto& line : m_lines){
		linecb.worldViewProj = line->m_world * m_camera.GetView() * m_camera.GetProj();
		linecb.worldViewProj = XMMatrixTranspose(linecb.worldViewProj);
		m_lineShader.Update(&linecb, TODO, TODO);
		line->Render();
	}
	m_pSwapChain->Present( 0, 0 );
}

LRESULT CALLBACK Application::MsgProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
	PAINTSTRUCT ps;
	HDC hdc;

	switch( message )
	{
	case WM_PAINT:
		hdc = BeginPaint( hWnd, &ps );
		EndPaint( hWnd, &ps );
		break;

	case WM_DESTROY:
		PostQuitMessage( 0 );
		break;

	case WM_KEYDOWN:
		app->m_keysDown[wParam] = true;
		break;

	case WM_KEYUP:
		app->m_keysDown[wParam] = false;
		app->m_speed = app->m_baseSpeed;
		break;

	case WM_MOUSEMOVE:
		app->m_cursorPos[0] = LOWORD(lParam);
		app->m_cursorPos[1] = HIWORD(lParam);
		break;

	case WM_LBUTTONDOWN:
		app->m_keysDown[VK_LBUTTON] = true;
		app->m_cursorPrePos[0] = LOWORD(lParam);
		app->m_cursorPrePos[1] = HIWORD(lParam);
		break;

	case WM_LBUTTONUP:
		app->m_keysDown[VK_LBUTTON] = false;
		break;
		
	case WM_RBUTTONDOWN:
		app->m_keysDown[VK_RBUTTON] = true;
		break;
	
	case WM_RBUTTONUP:
		app->m_keysDown[VK_RBUTTON] = false;
		break;

	default:
		return DefWindowProc( hWnd, message, wParam, lParam );
	}

	return 0;
}

HRESULT Application::InitResource()
{
	//init shader
	HRESULT hr;
	hr = m_meshShader.Init(
		m_pDevice, 
		m_pImContext, 
		L"mesh",
		D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST,
		meshLayout,
		nMeshLayout,
		sizeof(MeshCBuffer)
	);
	assert(SUCCEEDED(hr));
	hr = m_lineShader.Init(
		m_pDevice,
		m_pImContext,
		L"line", 
		D3D11_PRIMITIVE_TOPOLOGY_LINELIST,
		lineLayout,
		nLineLayout,
		sizeof(LineCBuffer)
	);
	assert(SUCCEEDED(hr));
	//init geo
	for(auto& mesh : m_meshes){ 
		hr = mesh->Init(m_pDevice, m_pImContext);
		assert(SUCCEEDED(hr));
	}
	for(auto& line : m_lines){
		hr = line->Init(m_pDevice, m_pImContext);
		assert(SUCCEEDED(hr));
	}
	//init texture
	hr = D3DX11CreateShaderResourceViewFromFile(m_pDevice, L"", nullptr, nullptr, &m_texView, nullptr);
	assert(SUCCEEDED(hr));

	return S_OK;
}

int Application::MainLoop()
{
	InitResource();
	// Main message loop
	MSG msg = {0};
	while( WM_QUIT != msg.message ){
		if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ){
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}else{
			m_timer.Tick();
			if(m_timer.IsStoped()){
				Sleep(100);
			}else{
				//update scene
				auto deltaTime = m_timer.GetDeltaTime();
				m_speed += deltaTime * m_acc;
				if(m_keysDown['W'] == true)
					m_camera.Move(AXIS_Z, m_speed * deltaTime);
				if(m_keysDown['S'] == true)
					m_camera.Move(AXIS_Z, -m_speed * deltaTime);
				if(m_keysDown['D'] == true)
					m_camera.Move(AXIS_X, m_speed * deltaTime);
				if(m_keysDown['A'] == true)
					m_camera.Move(AXIS_X, -m_speed * deltaTime);
				//view vector
				if(m_keysDown[VK_LBUTTON] == true){
					auto delta = m_cursorPos - m_cursorPrePos;
					m_camera.Rotate(AXIS_Y, delta[0]*XM_PI/360.f);
					m_camera.Rotate(AXIS_X, delta[1]*XM_PI/360.f);
					m_cursorPrePos = m_cursorPos;
				}
				m_camera.Update();
				Render();
			}
		}
	}
	return 1;
}

void Application::LoadGeometry(Geometry* obj, GEOMETRY_TYPE type)
{
	switch(type){
	case GEOMETRY_TYPE_MESH:
		m_meshes.push_back(obj);
		break;
	case GEOMETRY_TYPE_LINE:
		m_lines.push_back(obj);
		break;
	default:
		break;
	}

}

void Application::LoadCamera(const Camera& camera)
{
	m_camera = camera;
	m_camera.SetAspect((float)m_width/m_height);
}
